{***************************************************************
    SpyGestion  Copyright (C) 2008 Christophe KOALI <nature2gas@gmail.com>
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
****************************************************************}
unit modAlarmeAttaque;

interface

uses modFlotteVaisseauxChoix, modBaseDeDonnesRapport,
     modBaseType,
     Classes,
     SysUtils,
     modApercuMsg,

     sFonctionsChaines;


type
    TAlarmeUID = record
       DateImpact: int64;
       Location  : int64;
    end;
    PAlarmeUID = ^TAlarmeUID;


const
    CNT_ALERTE_ATT_HUMAN_NOT_NOTIFIED            = 0;
    CNT_ALERTE_ATT_HUMAN_STOP_NOTIFICATION       = 1;
    CNT_ALERTE_ATT_HUMAN_STOP_NOTIF_AVANT_INPACT = 2;
    CNT_ALERTE_ATT_HUMAN_STOP_EVACUATION         = 4;


    CNT_ALERTE_ATT_OPERATION_TROUVE              = 1;
    


    VAR_PARAM_PROG_DEPART_COORD  = '$depart_coord$';
    VAR_PARAM_PROG_ARRIVEE_COORD = '$arrivee_coord$';
    VAR_PARAM_PROG_IMPACT_DANS   = '$impact_duration$';

    VAR_PARAM_PROG_UNIVERS       = '$univers$';
    



type
    TAlarmeAttackEvacuationAuto = (aaeNonDefinit,
                                   aaeNeRienFaire,
                                   aaeEvacuerEnAttente,
                                   aaeAEteEvacue);


    TAlarmeAttack = record
        // Combine l'impact serveur et la planete de depart/arrive
        // calcule avec alarmGetUID
        // Ce N'EST PAS et ce NE DOIT PAS être un uid aléatoire ou séquentiel
        uid: TAlarmeUID;

        Depart : TPlanetLocation;
        Arrivee: TPlanetLocation;

        DateImpactServeur: int64;
        DateImpactLocal  : int64;

        // Le délai doit être aléatoire, pour éviter d'obtenir un nombre
        // aléatoire à chaque fois (et donc à un moment donné obtenir un petit nombre)
        // on stock ici la valeur du nombre obtenu et on utilise ensuite ce délai
        DelaiAvantImpactEva: integer;


        ConnuDepuis: int64;

        // Determine combien de fois un signal a été émis
        SignalBeepNb: integer;
        SignalMusiqueNb: integer;
        ProgramLauched : boolean;

        // Si l'humain a désactivé les notifications car il s'en est aperçu
        // on lui remet un coup de notification quelquee minutes avant l'impact
        // cette variable indique le nombre de notification ajouté
        NbBoucleOverride: integer;

        
        // définit si l'humain a été averti
        // cela permet de stoppé les alarmes
        HumainSoccupeDuReste: integer;
        
        // pour déterminer si l'attaque a été annulé
        FlotteExistePlus: boolean;
        FlotteDateCancel: int64;     // (date local du pc)
        FlotteExistePlusNotified: boolean;


        // Pour permettre de savoir si l'attaquant va gagner la bataille ou pas
        ScoreFlotteAQuai: integer;
        ScoreDefense    : integer;
        ScoreFlotteAttaquante: integer;

        VaisseauxAttaquant: string;

        // status de l'évacuation automatique
        // (uniquement si l'humain ne s'occupe pas du reste)
        EvacuationAutomatique: TAlarmeAttackEvacuationAuto;
    end;
    PAlarmeAttack = ^TAlarmeAttack;



    TAlarmeOnAttack    = procedure(Infos: PAlarmeAttack) of object;



    TCollectionAlarmeAttack = class(TList)
    private
       {--------------------------------------------------}
       {Elements spécialisés de la classes}
       FOnNewAttack    : TAlarmeOnAttack;
       FOnAttackStopped: TAlarmeOnAttack;


       {--------------------------------------------------}
       {Gestion des pointeurs}
       // Utilisé pour la propriété Itemx
       function GetItemx(Index: integer): TAlarmeAttack;


       procedure SetFlagFlotteExistePlus;
    public
       SignalBeepLastDate   : int64;
       SignalMusiqueLastDate: int64;

       property OnAttackNewDetected: TAlarmeOnAttack read FOnNewAttack     write FOnNewAttack;
       property OnAttackAnnule     : TAlarmeOnAttack read FOnAttackStopped write FOnAttackStopped;


       function TraiteMsgList(MsgList: TCollectionAppMessage): integer;

       function AjoutEnnemiFlotte(ImpactServ: int64; Depart, Arrivee: TPlanetLocation): PAlarmeAttack;

       function IsAttacked: boolean;


       {--------------------------------------------------}
       {Gestion des pointeurs}
       // *** Ajout de pointeurs
       // Renvoi le pointeur de l'élément ajouté
       function Add(Element: TAlarmeAttack): PAlarmeAttack;
       function AddFromMsg(Element: PAppMessage): PAlarmeAttack;

       // Simple ajout de pointeur
       procedure SimpleAdd(Pointeur: PAlarmeAttack);

       // Renvoi le pointeur de l'élément ajouté/modifié
       // Utilise Get_Index pour vérifier si l'élément doit être mis à jour
       function Add_Update(Element: TAlarmeAttack): PAlarmeAttack;


       // *** Suppression de pointeur
       // Libère et supprime le pointeur Index
       // Renvoi True si réussit, False si hors limite ou erreur
       function Delete(Index: integer): boolean;


       // *** Effacement de la liste de pointeur
       procedure Clear; override;


       // *** Requete sur la liste de pointeur
       function Get_Index(Element: PAlarmeUID): integer;
       function Get_Pointeur(Element: PAlarmeUID): PAlarmeAttack;
       function Get_Item(Index: integer): TAlarmeAttack;
       property Itemx[Index: integer]: TAlarmeAttack read GetItemx;


       // Pour le trie des éléments
       procedure Trie;


       // l'humain vient de prendre conscience des attaques
       procedure SetHumanFlag;


       {--------------------------------------------------}
       {Procédures et fonctions spécialisées de la classes}
       // Destructeur
       destructor Destroy; override;
       constructor Create;

    end;



    function hashCalculDepartArrivee(Depart, Arrivee: TPlanetLocation): integer;


implementation

uses ESBDates;


function maaCompareUIDAreEgal(u1, u2: PAlarmeUID): boolean;
begin
    Result := False;
    if u1.DateImpact = u2.DateImpact then begin
       if u1.Location = u1.Location then begin
          Result := true;
       end;
    end;
end;


function hashCalculDepartArrivee(Depart, Arrivee: TPlanetLocation): integer;
var x: int64;
begin

    // la position tient sur 7 bits:
    // 15 = 1111
    // 115 = 1110011  (lune)
    // Le système solaire tient sur 9 bits
    // 499 = 111110011
    // la galaxie tient sur 4 bits
    // 9 = 1001

    //   +-------------------------+----------+---------+
    //   |    Système solaire      | Position | Galaxie |
    //   +-------------------------+----------+---------+
    //            10 bits             7 bits      4 bits
    //
    // Problème: 19 bits
    // et 19 bits * 2 = 38 bits
    // or on a seulement la place pour 32 bits


    x := Depart.Galaxie  or (Depart.Position  shl 4) or (Depart.SystemSolaire  shl 12);
    Result := x;

    x := Arrivee.Galaxie or (Arrivee.Position shl 4) or (Arrivee.SystemSolaire shl 12);
    x := x shl 31;

    Result := Result or x
end;

function alarmGetUID(ImpactServ: int64; Depart, Arrivee: TPlanetLocation): TAlarmeUID;
begin
    Result.DateImpact := ImpactServ;
    Result.Location   := hashCalculDepartArrivee(Depart, Arrivee);
end;

function alarmGetUIDmsg(pmsg: PAppMessage): TAlarmeUID;
begin
   Result := alarmGetUID(pmsg.DateImpact, pmsg.Depart, pmsg.Arrivee);
end;


{*******************************************************************************
 ******************************************************************************* 
 **               TCollectionAlarmeAttack                                     **
 *******************************************************************************
 *******************************************************************************}

procedure TCollectionAlarmeAttack.SetHumanFlag;
var i: integer;
    pe: PAlarmeAttack;
begin

   if Count = 0 then begin
      exit;
   end;

   for i := 0 to Count - 1 do begin
      pe := Items[i];

      pe.HumainSoccupeDuReste := CNT_ALERTE_ATT_HUMAN_STOP_NOTIFICATION;
   end;

end;

function TCollectionAlarmeAttack.IsAttacked: boolean;
var i: integer;
    peAtt: PAlarmeAttack;
    CurrDate: int64;
begin

    Result := False;
    if Count = 0 then exit;

    CurrDate := DateTimeToUnix(Now);


    for i := 0 to Count - 1 do begin
        peAtt := Items[i];

        if peAtt.HumainSoccupeDuReste <> CNT_ALERTE_ATT_HUMAN_NOT_NOTIFIED then begin
           // l'humain a traité l'action
           // cependant il vaut mieux garder un bon taux de raffraichissement
           //Continue;
        end;

        if peAtt.FlotteExistePlus then begin
           // l'attaque n'existe plus
           Continue;
        end;

        if peAtt.DateImpactLocal <= CurrDate then begin
           // l'impact à déjà eut lieu
           Continue;
        end;

        // l'impact n'a pas encore eut lieu
        Result := true;
        exit;

    end;

end;

function TCollectionAlarmeAttack.TraiteMsgList(MsgList: TCollectionAppMessage): integer;
label lFin, lAjout;
var i: integer;
    pe: PAppMessage;
    peAtt: PAlarmeAttack;
    tAtt: TAlarmeAttack;

    cUID: TAlarmeUID;
    CurrDate: int64;
begin

    Result := 0;

    if MsgList = nil then begin
       // problème...
       exit;
    end;
    
    SetFlagFlotteExistePlus;

    if MsgList.Count = 0 then goto lFin;



    CurrDate := DateTimeToUnix(Now);

    for i := 0 to MsgList.Count - 1 do begin
        pe := MsgList.Items[i];

        if (pe.bColorMenace)  then begin

           if (pe.Ordre = ooAttaquer) then begin
              // flotte ennemi non pacifique
              // verifie si elle n'existe pas déjà

              cUID := alarmGetUIDmsg(pe);

              peAtt := Get_Pointeur(@cUID);
              if peAtt = nil then begin
lAjout:
                 // non existant
                 // nouvelle attaque détectée
                 peAtt := AddFromMsg(pe);


                 peAtt.DateImpactLocal := CurrDate + pe.Duree - 3;

                 if Assigned(FOnNewAttack) then begin
                    FOnNewAttack(peAtt);
                 end;

              end;


              // la flotte existe toujours
              peAtt.FlotteExistePlus := false;



           end else if pe.Ordre = ooEspionner then begin
              // on se fait espionner

              cUID  := alarmGetUIDmsg(pe);
              peAtt := Get_Pointeur(@cUID);

              // non existant, la flotte est nouvelle
              if peAtt = nil then begin

                 // il faut 30 min à un VB (le vaisseau le plus rapide)
                 // pour atteindre une planete
                 // A cela, on doit soustraire l'intervalle de verification
                 // on utilise donc 10 mins
                 if pe.Duree > 600 then begin
                    // on classe la flotte comme attaquante
                    goto lAjout;

                 end;

              end else begin
                 // la flotte existe toujours ET elle a déjà été mémorisé
                 peAtt.FlotteExistePlus := false;
              end;



           end;


        end


    end;


lFin:
    // traite les messages de vaisseaux qui n'existe plus
    if Count > 0 then begin
       for i := 0 to Count - 1 do begin
          peAtt := Items[i];

          if peAtt.FlotteExistePlus then begin
             // ok, n'existe plus
             if peAtt.FlotteDateCancel < 100 then begin
                // c'est à l'instant
                peAtt.FlotteDateCancel := CurrDate;
                peAtt.FlotteExistePlusNotified := false;


                if Assigned(FOnAttackStopped) then begin
                   // déclenche l'évènement
                   FOnAttackStopped(peAtt);

                end;
             end;

          end;

       end;

    end;

end;


function TCollectionAlarmeAttack.AjoutEnnemiFlotte(ImpactServ: int64; Depart, Arrivee: TPlanetLocation): PAlarmeAttack;
begin
    Result := nil;
end;


function CompareElements(Item1, Item2: Pointer): Integer;
var e1, e2: PAlarmeAttack;
begin


    // Compare renvoie < 0 si Item1 est inférieur à Item2,
    //                   0 s'ils sont égaux
    //                 > 0 si Item1 est supérieur à Item2
    // Voir http://alphomega.developpez.com/TriTList/
    // pour savoir comment trier sur plusieurs critére

    e1 := Item1;
    e2 := Item2;


    // Ici on ne compare que deux entiers, donc:
    if e1.DateImpactLocal < e2.DateImpactLocal then
       // Item1 est inférieur à Item2
       Result := -1

    else if e1.DateImpactLocal = e2.DateImpactLocal then
       // Egalité
       Result := 0

    else
       // Sinon Item1 est supérieur à Item2
       Result := 1;

end;


procedure TCollectionAlarmeAttack.Trie;
begin
    Sort(CompareElements);
end;


constructor TCollectionAlarmeAttack.Create;
begin

    // Héritage
    InHerited Create;
    
    
    
    // Code pour l'initialisation ici
       SignalBeepLastDate   := 0;
       SignalMusiqueLastDate:= 0;


end;

destructor TCollectionAlarmeAttack.Destroy;
begin
    // Implémenter le code avant l'héritage de la méthode
    // C'est à dire ici
    
   
    // On appelle NOTRE clear qui lui-même appelle NOTRE Delete qui libère l'espace mémoire
    // avant d'enlever le pointeur de la TList
    // L'héritage de Destroy appelle Clear, mais es-ce le notre ou celui de la classe de base ?
    Clear;
    
    
    // Héritage
    InHerited Destroy;
end;


procedure TCollectionAlarmeAttack.Clear;
var i: integer;
begin

    if Count > 0 then begin
        For i := Count -1 downto 0 do
            Delete(i);
    end;
    
    inherited Clear;
end;

procedure TCollectionAlarmeAttack.SetFlagFlotteExistePlus;
var i: integer;
    pElement: PAlarmeAttack;
begin

   if Count <= 0 then exit;

   for i := 0 to Count - 1 do begin
      pElement := Items[i];
      pElement.FlotteExistePlus := true;
   end;


end;

function TCollectionAlarmeAttack.Get_Pointeur(Element: PAlarmeUID): PAlarmeAttack;
var i: integer;
begin

   Result := nil;
   
   i := Get_Index(Element);
   if i < 0 then exit;
   Result := Items[i];

end;


function TCollectionAlarmeAttack.Get_Index(Element: PAlarmeUID): integer;
var i: integer;
    pElement: PAlarmeAttack;
begin

   Result := -1;

   if Count <= 0 then exit;

   for i := 0 to Count - 1 do begin
      pElement := Items[i];

      {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
      {### Neccessite adpation en fonction du type record  ### }
      {### ### ### ### ### ### ### ### ### ### ### ### ### ### }


      if maaCompareUIDAreEgal(@(pElement.uid), Element) then begin
         Result := i;
         exit;
      end;
   end;

end;



function TCollectionAlarmeAttack.Add_Update(Element: TAlarmeAttack): PAlarmeAttack;
var i : integer;
begin
     
     Result := nil;
                                         {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
     i := Get_Index(@Element.uid);        {### Neccessite adpation en fonction du type record  ### }
                                         {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
     if i < 0 then begin 
        // L'élément n'existe pas, l'ajoute
        Result  := Add(Element);
     end else begin
        // L'élément existe déjà, le met à jour
        Result  := Items[i];     // Obtient le pointeur
        Result^ := Element;
     end;
     
end;




function TCollectionAlarmeAttack.Get_Item(Index: integer): TAlarmeAttack;
var pElement: PAlarmeAttack;
begin

     if Index < 0 then exit;
     if Index >= Count then exit;

     pElement := Items[Index];     // Obtient le pointeur
     Result := pElement^;

end;

function TCollectionAlarmeAttack.Delete(Index: integer): boolean;
var pElement: PAlarmeAttack;
begin

    Result := false;

    if (Index < 0) or (Index >= Count) then
       // Hors limite
       exit;


    // Obtient le pointeur
    pElement := Items[Index];

    if pElement <> nil then
       // Libére la mémoire créée lors de l'ajout
       Dispose(pElement);

    // Supprime le pointeur de la liste
    inherited Delete(Index);

    // Met le resultat à True
    Result := true;

end;


function TCollectionAlarmeAttack.AddFromMsg(Element: PAppMessage): PAlarmeAttack;
var pE: PAlarmeAttack;
begin

    Result := nil;

    New(pE);          // Créer l'espace mémoire requis


    pE.uid          := alarmGetUIDmsg(Element);
    pE.Depart       := Element.Depart;
    pE.Arrivee      := Element.Arrivee;
    pE.DateImpactServeur := Element.DateImpact;
    pE.DateImpactLocal  := 0;
    pE.ConnuDepuis      := DateTimeToUnix(Now);

    pE.DelaiAvantImpactEva := -1;
    

    pE.SignalBeepNb     := 0;
    pE.SignalMusiqueNb  := 0;
    pE.ProgramLauched   := false;

    pe.NbBoucleOverride := 0;



    pe.HumainSoccupeDuReste := CNT_ALERTE_ATT_HUMAN_NOT_NOTIFIED;


    pE.FlotteExistePlus := false;
    pE.FlotteDateCancel := 0;
    pE.FlotteExistePlusNotified := false;


    pE.ScoreFlotteAQuai     := -1;
    pE.ScoreFlotteAttaquante:= -1;
    pE.ScoreDefense         := -1;
    pE.VaisseauxAttaquant   := Element.VaisseauxStr;


    pE.EvacuationAutomatique := aaeNonDefinit;

    
    try
       inherited Add(pE);  // Ajoute le pointeur à la liste
       Result := pE;
    except
       Result := nil;
    end;

end;


function TCollectionAlarmeAttack.Add(Element: TAlarmeAttack): PAlarmeAttack;
var pElement: PAlarmeAttack;
begin

    Result := nil;

    New(pElement);          // Créer l'espace mémoire requis
    pElement^ := Element;   // Recopie les données dans l'emplacement pointé

    try
       inherited Add(pElement);  // Ajoute le pointeur à la liste
       Result := pElement;
    except
       Result := nil;
    end;

    // Pour libérer la mémoire occupé par l'élément pointer:  Dispose(pElement);

end;

// Simple ajout de pointeur
procedure TCollectionAlarmeAttack.SimpleAdd(Pointeur: PAlarmeAttack);
begin
    
    inherited Add(Pointeur);  // Ajoute le pointeur à la liste
    
end;

function TCollectionAlarmeAttack.GetItemx(Index: integer): TAlarmeAttack;
var pElement: PAlarmeAttack;
begin

   if Count < 0 then exit;
   if Index >= Count then exit;

   pElement := Items[Index];
   Result := pElement^;

end;


end.
